package redisserver

import (
	"codehub-g.huawei.com/videocloud/mediaprocesscenter/application-gateway/pkg/utils/log"
	"fmt"
	"github.com/go-redis/redis/v7"
	"github.com/pkg/errors"
	"time"
)

const (
	RedisRetryTimes = 3
	RedisSleepTime  = 1
)

// DeleteProcessFromRedis 从ZSet中删除进程
func DeleteProcessFromRedis(fleetId string, processId string) error {
	RedisClient := GetRedisClient()
	defer CloseRedisClient(RedisClient)
	log.RunLogger.Infof("[Redis] begin to delete process %s from fleet %s", processId, fleetId)
	var err error
	// 如果当前进程是ZSet中最后一个成员，是无法删除的（数据结构决定），需要删除整个ZSet，此时fleet没有active进程。后续有active进程上报时，会重建ZSet
	for retry := RedisRetryTimes; retry > 0; retry-- {
		luaScript := `
			local count = redis.call('ZCARD', KEYS[1]) -- 获取ZSet的成员数量
			if count > 1 then
				redis.call('ZREM', KEYS[1], KEYS[2]) -- 删除目标进程
				return 0
			elseif count == 1 then
				redis.call('DEL', KEYS[1]) -- 删除整个ZSet
				return 1
			end
		`
		_, err = RedisClient.Eval(luaScript, []string{fleetId, processId}).Result()
		if err != nil {
			log.RunLogger.Errorf("[Redis] delete process %s in redis failed %v, current retry %v", processId, err, RedisRetryTimes-retry)
			time.Sleep(RedisSleepTime * time.Second)
			continue
		}
		return nil
	}
	return err
}

// RegisterProcessToRedis 进程注册
func RegisterProcessToRedis(fleetId string, processId string, availableCount int) error {
	RedisClient := GetRedisClient()
	defer CloseRedisClient(RedisClient)
	log.RunLogger.Infof("[Redis] begin to register process %s to fleet %s", processId, fleetId)
	redisRes := RedisClient.ZScore(fleetId, processId)
	_, err := redisRes.Result()
	if errors.Is(err, redis.Nil) {
		AddProcessToRedis(fleetId, processId, availableCount)
		return nil
	}
	if err == nil {
		return fmt.Errorf("[Redis] this process %s is in redis now", processId)
	}
	return fmt.Errorf("[Redis] check process %s score from redis failed for %v", processId, err)
}

// CheckProcessFromRedis 从ZSet中审计是否有目标进程
func CheckProcessFromRedis(fleetId string, processId string) error {
	RedisClient := GetRedisClient()
	defer CloseRedisClient(RedisClient)
	log.RunLogger.Infof("[Redis] begin to check process %s in fleet %s or not", processId, fleetId)
	redisRes := RedisClient.ZScore(fleetId, processId)
	_, err := redisRes.Result()
	if err != nil {
		log.RunLogger.Errorf("[Redis] check process %s in redis failed for %v", processId, err)
		return err
	}
	return nil
}

// AddProcessToRedis 将进程信息加入至ZSet
func AddProcessToRedis(fleetId string, processId string, availableCount int) {
	RedisClient := GetRedisClient()
	defer CloseRedisClient(RedisClient)
	log.RunLogger.Infof("[Redis] begin to register process %s to fleet %s", processId, fleetId)
	zSetParam := redis.Z{Member: processId, Score: float64(availableCount)}
	redisRes := RedisClient.ZAdd(fleetId, &zSetParam)
	_, err := redisRes.Result()
	if err != nil {
		// 写入失败不回滚，下次进程健康上报时再尝试写入
		log.RunLogger.Errorf("[Redis] create process in redis failed for %v", err)
	}
}

// PickProcessFromRedis 获取可用进程
func PickProcessFromRedis(fleetId string) (string, error) {
	/*
		Exec + TxPipeline 可以实现Redis命令批量绑定执行，但难以插入中间步骤结果的判断；此处使用原生Lua脚本以加入判断逻辑
		Lua脚本数组下标从1开始
	*/
	RedisClient := GetRedisClient()
	defer CloseRedisClient(RedisClient)
	luaScript := `
        local member = redis.call('ZRevRange', KEYS[1], 0, 0, 'WITHSCORES') -- 获取可用会话数最高的进程
		if not member or not next(member) then
			return 0
        end
		local score = tonumber(member[2]) -- 若可用会话数已为0，则为无可用进程，直接返回
		if score > 1e-9 then
			local result = redis.call('ZIncrBy', KEYS[1], -1, member[1])
			return member[1]
		end
		return 1
    `
	result, err := RedisClient.Eval(luaScript, []string{fleetId}).Result()
	if err != nil {
		log.RunLogger.Errorf("[Redis] run lua script failed for %v", err)
		return "", err
	}
	if _, ok := result.(int64); ok {
		log.RunLogger.Errorf("[Redis] there is no available process in fleet %s", fleetId)
		return "", fmt.Errorf("there is no available process in fleet %s", fleetId)
	}
	if processId, ok := result.(string); ok {
		return processId, nil
	}
	return "", fmt.Errorf("[Redis] get process from redis failed, result %v is not in string or bool type", result)
}

// IncrServerSessionCountToRedis 进程可用会话数+1
func IncrServerSessionCountToRedis(fleetId string, processId string) error {
	// 操作前确保有该进程，否则可能出现进程非active与会话终止同时上报时，通过该操作将非active进程再次写入
	luaScript := `
		if redis.call("ZSCORE", KEYS[1], KEYS[2]) then
    		redis.call("ZINCRBY", KEYS[1], 1, KEYS[2])
			return 1
		end
		return 0
	`
	RedisClient := GetRedisClient()
	defer CloseRedisClient(RedisClient)
	log.RunLogger.Infof("[Redis] begin to add server session count to process %s in fleet %s", processId, fleetId)
	result, err := RedisClient.Eval(luaScript, []string{fleetId, processId}).Result()
	if err != nil {
		log.RunLogger.Errorf("[Redis] add available server session to process %s in redis failed for %v", processId, err)
		return err
	}
	if val, ok := result.(int64); ok {
		if val == 0 {
			log.RunLogger.Infof("[Redis] current process %s is not in redis, incr failed", processId)
		}
	}
	return nil
}

// DecrServerSessionFromRedis 当数据库会话计数-1失败时，认为该会话仍然占用资源，Redis可用会话-1
func DecrServerSessionFromRedis(fleetId string, processId string) {
	// 操作前确保有该进程，否则可能出现进程非active与会话终止同时上报时，通过该操作将非active进程再次写入
	luaScript := `
		if redis.call("ZSCORE", KEYS[1], KEYS[2]) then
    		redis.call("ZINCRBY", KEYS[1], -1, KEYS[2])
			return 1
		end
		return 0
	`
	RedisClient := GetRedisClient()
	defer CloseRedisClient(RedisClient)
	log.RunLogger.Infof("[Redis] begin to decr server session count to process %s in fleet %s", processId, fleetId)
	result, err := RedisClient.Eval(luaScript, []string{fleetId, processId}).Result()
	if err != nil {
		log.RunLogger.Errorf("[Redis] decr available server session to process %s in redis failed for %v", processId, err)
	}
	if val, ok := result.(int64); ok {
		if val == 0 {
			log.RunLogger.Infof("[Redis] current process %s is not in redis, decr failed", processId)
		}
	}
}
